package com.caipos.actor.ruleChain;

import com.caipos.actor.ActorSystemContext;
import com.caipos.actor.common.actors.KbActorCtx;
import com.caipos.actor.common.actors.KbActorRef;
import com.caipos.actor.common.actors.KbEntityActorId;
import com.caipos.actor.common.data.EntityType;
import com.caipos.actor.common.data.constant.ComponentLifecycleState;
import com.caipos.actor.common.data.id.RuleChainId;
import com.caipos.actor.common.data.id.RuleNodeId;
import com.caipos.actor.common.data.id.TenantId;
import com.caipos.actor.common.data.relation.Relation;
import com.caipos.actor.common.data.rule.RuleChain;
import com.caipos.actor.common.data.rule.RuleNode;
import com.caipos.actor.common.msg.KbMsg;
import com.caipos.actor.service.DefaultActorService;
import com.caipos.actor.shared.ComponentMsgProcessor;
import com.caipos.service.RuleChainService;
import com.caipos.service.RuleNodeService;
import com.caipos.util.RuleNodeException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

@Slf4j
public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {

    private static final String NA_RELATION_TYPE = "";
    private final KbActorRef parent;
    private final KbActorRef self;
    private final Map<String, RuleNodeCtx> nodeActors;
    private final Map<String, List<RuleNodeRelation>> nodeRoutes;
    private final RuleChainService service;
    private String ruleChainName;

    private String firstId;
    private RuleNodeCtx firstNode;
    private boolean started;

    RuleChainActorMessageProcessor(TenantId tenantId, RuleChain ruleChain, ActorSystemContext systemContext, KbActorRef parent, KbActorRef self) {
        super(systemContext, tenantId, new RuleChainId(ruleChain.getId()));
        this.ruleChainName = ruleChain.getName();
        this.parent = parent;
        this.self = self;
        this.nodeActors = new HashMap<>();
        this.nodeRoutes = new HashMap<>();
        this.service = systemContext.getRuleChainService();
    }

    @Override
    public String getComponentName() {
        return ruleChainName;
    }

    @Override
    public void start(KbActorCtx context) {
        if (!started) {
            RuleChain ruleChain=service.getRuleChainById(entityId.toString());
            if (ruleChain != null) {
                if("1c0101d0-e9c2-11ef-b320-8f2b08bb2546".equals(entityId.getId())){
                    System.err.println("sss");
                }

                List<RuleNode> ruleNodeList = service.getRuleChainNodesByRuleChainId(ruleChain.getId());
                log.debug("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
                // Creating and starting the actors;
                for (RuleNode ruleNode : ruleNodeList) {
                    log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
                    KbActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
                    nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
                }
                initRoutes(ruleChain, ruleNodeList);
                started = true;
            }
        } else {
            onUpdate(context);
        }
    }

    private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) {
        nodeRoutes.clear();
        // Populating the routes map;
        for (RuleNode ruleNode : ruleNodeList) {
            List<Relation> relations = service.getRuleNodeRelations(ruleNode);
            log.trace("[{}][{}][{}] Processing rule node relations [{}]", tenantId, entityId, ruleNode.getId(), relations.size());
            if (relations.isEmpty()) {
                nodeRoutes.put(ruleNode.getId(), Collections.emptyList());
            } else {
                for (Relation relation : relations) {
                    log.trace("[{}][{}][{}] Processing rule node relation [{}]", tenantId, entityId, ruleNode.getId(), relation.getToType());
                    if (relation.getToType() == EntityType.RULE_NODE) {
                        RuleNodeCtx ruleNodeCtx = nodeActors.get(relation.getToId());
                        if (ruleNodeCtx == null) {
                            throw new IllegalArgumentException("Rule Node [" + relation.getFromId() + "] has invalid relation to Rule node [" + relation.getToId() + "]");
                        }
                    }
                    nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>())
                            .add(new RuleNodeRelation(relation.getFromId(), relation.getToId(), relation.getRelationType()));
                }
            }
        }

        firstId = ruleChain.getFirstRuleNodeId();
        firstNode = nodeActors.get(firstId);
        state = ComponentLifecycleState.ACTIVE;
    }

//    public List<RuleNode> getRuleNodes() throws JsonProcessingException {
//        List<RuleNode> ruleNodes = new ArrayList<>();
//        ObjectMapper objectMapper = new ObjectMapper();
//        RuleNode ruleNode = RuleNode.builder()
//                .ruleChainId("b87e32f0-e8f3-11ef-b4e2-537c76c4bb45")
//                .type("org.thingsboard.rule.engine.filter.KbMsgTypeSwitchNode")
//                .name("测试")
//                .singletonMode(false)
//                .configurationVersion(0L)
//                .configuration(objectMapper.readTree("{\"version\":0}"))
//                .build();
//        ruleNode.setId("251b6bb0-e90f-11ef-b4e2-537c76c4bb45");
//        ruleNodes.add(ruleNode);
//        return ruleNodes;
//    }

    @Override
    public void onUpdate(KbActorCtx context) {
        RuleChain ruleChain = service.getRuleChainById(tenantId.getId().toString());
        if (ruleChain != null) {
            ruleChainName = ruleChain.getName();
//            List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
//            log.debug("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
//            for (RuleNode ruleNode : ruleNodeList) {
//                RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
//                if (existing == null) {
//                    log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
//                    KbActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
//                    nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
//                } else {
//                    log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
//                    existing.setSelf(ruleNode);
//                    existing.getSelfActor().tellWithHighPriority(new RuleNodeUpdatedMsg(tenantId, existing.getSelf().getId()));
//                }
//            }

//            Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());
//            List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).toList();
//            removedRules.forEach(ruleNodeId -> {
//                log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId);
//                RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
//                removed.getSelfActor().tellWithHighPriority(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED));
//            });
//
//            initRoutes(ruleChain, ruleNodeList);
        }
    }

    @Override
    protected RuleNodeException getInactiveException() {
        RuleNode firstRuleNode = firstNode != null ? firstNode.getSelf() : null;
        return new RuleNodeException("Rule Chain is not active!  Failed to initialize.", ruleChainName, firstRuleNode);
    }

    @Override
    public void stop(KbActorCtx ctx) {
        log.trace("[{}][{}] Stopping rule chain with {} nodes", tenantId, entityId, nodeActors.size());
        nodeActors.clear();
        nodeRoutes.clear();
        started = false;
    }


    private KbActorRef createRuleNodeActor(KbActorCtx ctx, RuleNode ruleNode) {
        return ctx.getOrCreateChildActor(new KbEntityActorId(new RuleNodeId(ruleNode.getId())),
                () -> DefaultActorService.RULE_DISPATCHER_NAME,
                () -> new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleChainName, new RuleNodeId(ruleNode.getId())),
                () -> true);
    }


    private void onTellNext(KbMsg msg, boolean useRuleNodeIdFromMsg) {
        try {
            RuleNodeId targetId = useRuleNodeIdFromMsg ? msg.getRuleNodeId() : null;
            RuleNodeCtx targetCtx;
            if (targetId == null) {
                targetCtx = firstNode;
                msg = msg.copy()
                        .ruleChainId(entityId)
                        .resetRuleNodeId()
                        .build();
            } else {
                targetCtx = nodeActors.get(targetId);
            }
            if (targetCtx != null) {
                log.trace("[{}][{}] Pushing message to target rule node", entityId, targetId);
                pushMsgToNode(targetCtx, msg, NA_RELATION_TYPE);
            } else {
                log.trace("[{}][{}] Rule node does not exist. Probably old message", entityId, targetId);
            }
        } catch (Exception e) {
        }
    }


    private void pushMsgToNode(RuleNodeCtx nodeCtx, KbMsg msg, String fromRelationType) {
        if (nodeCtx != null) {
            var kbCtx = new DefaultKbContext(systemContext, ruleChainName, nodeCtx);
//            nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(tbCtx, msg, fromRelationType));
        } else {
            log.error("[{}][{}] RuleNodeCtx is empty", entityId, ruleChainName);
        }
    }

}
